package org.jetbrains.plugins.cucumber.groovy.resolve.noncode;
import com.intellij.psi.*;
import com.intellij.psi.impl.cache.CacheManager;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.UsageSearchContext;
import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.psi.util.PsiTreeUtil;
import org.codehaus.groovy.runtime.DefaultGroovyMethods;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.cucumber.groovy.GrCucumberCommonClassNames;
import org.jetbrains.plugins.cucumber.groovy.GrCucumberUtil;
import org.jetbrains.plugins.groovy.GroovyFileType;
import org.jetbrains.plugins.groovy.lang.psi.GroovyFile;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrStatement;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentList;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrMethodCall;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMember;
import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames;
import org.jetbrains.plugins.groovy.lang.resolve.NonCodeMembersContributor;
import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil;
public class CustomWorldContributor extends NonCodeMembersContributor {
@Override
public void processDynamicElements(@NotNull PsiType qualifierType,
@NotNull PsiScopeProcessor processor,
@NotNull PsiElement place,
@NotNull ResolveState state) {
if (place instanceof GrReferenceExpression &&
((GrReferenceExpression)place).getQualifier() == null &&
qualifierType.equalsToText(GroovyCommonClassNames.GROOVY_LANG_CLOSURE)) {
final GrClosableBlock closureContainer = PsiTreeUtil.getParentOfType(place, GrClosableBlock.class, true, GrMember.class);
if (closureContainer != null) {
PsiElement parent = closureContainer.getParent();
if (parent instanceof GrArgumentList && isLastArg((GrArgumentList)parent, closureContainer)) {
parent = parent.getParent();
}
if (parent instanceof GrMethodCall && (GrCucumberUtil.isStepDefinition(parent) || GrCucumberUtil.isHook((GrMethodCall)parent))) {
doProcessDynamicMethods(processor, place, state, parent.getContainingFile());
}
}
}
}
private static void doProcessDynamicMethods(@NotNull PsiScopeProcessor processor,
@NotNull PsiElement place,
@NotNull ResolveState state,
final PsiFile stepFile) {
if (stepFile instanceof GroovyFile) {
final PsiType worldType = getWorldType((GroovyFile)stepFile);
if (worldType != null) {
ResolveUtil.processAllDeclarations(worldType, processor, state, place);
}
else {
GlobalSearchScope scope = GlobalSearchScope.getScopeRestrictedByFileTypes(stepFile.getResolveScope(),
GroovyFileType.getGroovyEnabledFileTypes());
PsiFile[] files = CacheManager.SERVICE.getInstance(place.getProject()).getFilesWithWord("World", UsageSearchContext.IN_CODE, scope, true);
for (PsiFile file : files) {
if (file instanceof GroovyFile) {
final PsiType type = getWorldType((GroovyFile)file);
if (type != null) {
if (!ResolveUtil.processAllDeclarations(type, processor, state, place)) {
return;
}
}
}
}
}
}
}
@Nullable
private static PsiType getWorldType(@NotNull final GroovyFile stepFile) {
return CachedValuesManager.getCachedValue(stepFile, () -> {
for (GrStatement statement : stepFile.getStatements()) {
if (statement instanceof GrMethodCall && isWorldDeclaration((GrMethodCall)statement)) {
final GrClosableBlock closure = getClosureArg((GrMethodCall)statement);
return CachedValueProvider.Result.create(closure == null ? null : closure.getReturnType(), stepFile);
}
}
return CachedValueProvider.Result.create(null, stepFile);
});
}
@Nullable
private static GrClosableBlock getClosureArg(@NotNull GrMethodCall methodCall) {
final GrClosableBlock[] closures = methodCall.getClosureArguments();
if (closures.length == 1) return closures[0];
if (closures.length > 1) return null;
final GrExpression[] args = methodCall.getExpressionArguments();
if (args.length == 0) return null;
final GrExpression last = DefaultGroovyMethods.last(args);
if (last instanceof GrClosableBlock) {
return (GrClosableBlock)last;
}
return null;
}
private static boolean isWorldDeclaration(@NotNull GrMethodCall methodCall) {
final GrExpression invoked = methodCall.getInvokedExpression();
if (invoked instanceof GrReferenceExpression) {
final PsiMethod method = methodCall.resolveMethod();
final PsiClass clazz = method == null ? null : method.getContainingClass();
final String qname = clazz == null ? null : clazz.getQualifiedName();
return method!= null && "World".equals(method.getName()) && GrCucumberCommonClassNames.isHookClassName(qname);
}
return false;
}
private static boolean isLastArg(@NotNull GrArgumentList list, @NotNull GrClosableBlock block) {
final GrExpression[] exprs = list.getExpressionArguments();
return exprs.length > 0 && exprs[exprs.length - 1] == block;
}
}